/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
 * PARTICULAR PURPOSE.
 * ***************************************************************************/


#include "stdafx.h"
#include <pgl/core/PGLContainer.h>
#include <pgl/core/PGLLegend.h>
#include <pgl/core/PGLRegion.h>
#include <pgl/core/PGLArchive.h>

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif


PGL_IMPLEMENT_DYNCREATE(CPGLContainer, CPGLObject);

//! Save settings
void CPGLContainer::SerializeXML(CPGLArchive& ar)
{
	static const TCHAR szTag[] = _T("CPGLContainer");
	static STransform tr;
	CPGLPointD origin, scaling;
	double angle;

	CString datas, str;
	std::_tstring strX,strY;

	markup::CMarkupArchive& xml = *((markup::CMarkupArchive*)ar.GetMarkup());
	if(ar.IsStoring())
	{
		xml.AddChildElem(szTag);
		xml.AddChildAttrib(_T("Name"),GetName());
		xml.IntoElem();
		
			// base class
			CPGLObject::SerializeXML(ar);

			xml.AddChildElem(_T("Objects"));
			xml.IntoElem();
				m_mObjects.SerializeXML(ar);
			xml.OutOfElem();

			xml.AddChildElem(_T("Transforms"));
			xml.IntoElem();

				// add transformations
				std::list<STransform>::const_iterator is;
				for (is = m_lTransforms.begin(); is != m_lTransforms.end() ; is ++)
				{
					tr = *is;
					switch(tr.type)
					{
					case STransform::TypeRotation:
						xml.AddChildElem(_T("Rotation"));
						xml.IntoElem();
							xml.AddChildElemEx(_T("XOrig"), tr.point.x);
							xml.AddChildElemEx(_T("YOrig"), tr.point.y);
							xml.AddChildElemEx(_T("Angle"), tr.dValue);
						xml.OutOfElem();
						break;	
					case STransform::TypeScaling:
						xml.AddChildElem(_T("Scaling"));
						xml.IntoElem();
							xml.AddChildElemEx(_T("XOrig"), tr.point.x);
							xml.AddChildElemEx(_T("YOrig"), tr.point.y);
							xml.AddChildElemEx(_T("XScale"), tr.poValue.x);
							xml.AddChildElemEx(_T("YScale"), tr.poValue.y);
						xml.OutOfElem();
						break;	
					}
				}

			xml.OutOfElem();
		xml.OutOfElem();
	}
	else
	{
		// erasing previous data
		DeleteAllObjects();
		ResetTransformations();

		if  (!xml.FindChildElem(szTag))
				return;

		xml.IntoElem();

			/////////////////////////////////////////////////////////
			// loading base class
			CPGLObject::SerializeXML(ar);

			xml.ResetChildPos();
			if (xml.FindChildElem(_T("Objects")))
			{
				xml.IntoElem();
					m_mObjects.SerializeXML(ar);
				xml.OutOfElem();
			}

			xml.ResetChildPos();
			if(xml.FindChildElem(_T("Transforms")))
			{
				xml.IntoElem();

					while ( xml.FindChildElem() )
					{
						str = xml.GetChildTagName();
						if (str == _T("Rotation"))
						{
							origin = CPGLPointD(0,0);
							angle = 0;
							xml.IntoElem();
								xml.FindGetChildData(_T("XOrig"),origin.x);
								xml.FindGetChildData(_T("YOrig"),origin.y);
								xml.FindGetChildData(_T("Angle"),angle);
							xml.OutOfElem();

							PushRotation(angle, origin);
						}
						else if (str == _T("Scaling"))
						{
							origin = CPGLPointD(0,0);
							scaling = CPGLPointD(1,1);
							xml.IntoElem();
								xml.FindGetChildData(_T("XOrig"),origin.x);
								xml.FindGetChildData(_T("YOrig"),origin.y);
								xml.FindGetChildData(_T("XScale"),scaling.x);
								xml.FindGetChildData(_T("XScale"),scaling.y);
							xml.OutOfElem();
							PushScaling( scaling, origin);

						}
					}
				xml.OutOfElem();
			}	

		xml.OutOfElem();

		PostUpdateExtent();
	}
}

#ifdef _DEBUG
void CPGLContainer::Dump( CDumpContext& dc ) const
{
    // call base class function first
    CPGLObject::Dump( dc );

    // now do the stuff for our specific class
	dc << _T("CPGLContainer ID ") << GetID() << "\n";
}
void CPGLContainer::AssertValid() const
{
    // call inherited AssertValid first
    CPGLObject::AssertValid();
} 
#endif


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CPGLContainer::CPGLContainer()
{
}

CPGLContainer::~CPGLContainer()
{
	DeleteAllObjects();
}

CPGLContainer& CPGLContainer::operator = (const CPGLContainer& st)
{
	if(this != &st)
	{
		this->CPGLObject::operator =(st);

	}
	return *this;
}

void CPGLContainer::PushRotation( double angleRad, const CPGLPointD& origin)
{
	STransform tr;
	tr.type = STransform::TypeRotation;
	tr.point = origin;
	tr.dValue = angleRad;

	m_lTransforms.push_back(tr);
}

void CPGLContainer::PushScaling( const CPGLPointD& scaling, const CPGLPointD& origin)
{
	STransform tr;
	tr.type = STransform::TypeScaling;
	tr.point = origin;
	tr.poValue = scaling;

	m_lTransforms.push_back(tr);
}

void CPGLContainer::PopTransformation()
{
	if (m_lTransforms.empty())
		return;

	m_lTransforms.pop_back();
}

void CPGLContainer::ResetTransformations()
{
	m_lTransforms.clear();
}

void CPGLContainer::AddObjectHead( CPGLObjectPtr pObject)
{
	pObject->SetParent(GetParent());
	m_mObjects.AddHead(pObject);
	PostUpdateExtent();
}

void CPGLContainer::AddObjectBack( CPGLObjectPtr pObject)
{
	pObject->SetParent(GetParent());
	m_mObjects.AddTail(pObject);
	PostUpdateExtent();
}

void CPGLContainer::DeleteAllObjects()
{
	m_mObjects.Clear();
}


void CPGLContainer::PlotGfx(gfxinterface::CGfxInterface& gfx)
{
	static STransform tr;
	double ratiox, ratioy;

	if(!IsVisible())
		return;
	if (!IsInLayer(gfx.GetLayer()))
		return;

	// save state
	gfx.PushState();

	// add transformations
	std::list<STransform>::const_iterator is;
	for (is = m_lTransforms.begin(); is != m_lTransforms.end() ; is ++)
	{
		tr = *is;
		switch(tr.type)
		{
		case STransform::TypeRotation:
			ratiox = gfx.GetWorldClipWidth() / gfx.GetBBClipWidth();
			ratioy = gfx.GetWorldClipHeight() / gfx.GetBBClipHeight();

			// scaling, unscaling...
			gfx.Scale( 1,ratiox/ratioy,tr.point.x, tr.point.y);
			gfx.Rotate(tr.dValue, tr.point.x, tr.point.y);
			gfx.Scale( 1,ratioy/ratiox,tr.point.x, tr.point.y);
			break;
		case STransform::TypeScaling:
			gfx.Scale(tr.poValue.x, tr.poValue.y, tr.point.x, tr.point.y);
			break;
		}
	}

	// now plotting the objects
	CPGLObjectManager::Container::iterator it;
	for (it=m_mObjects.begin();it!=m_mObjects.end();it++)
	{
		(*it)->PlotGfx(gfx);
	}

	//restoring state
	gfx.PopState();
}

void CPGLContainer::GetUnionExtent(gfxinterface::CGfxInterface& gfx, CPGLRectD& rExtent)
{
	CPGLObject::GetUnionExtent(gfx,rExtent);

	GetExtent().SetUnion(rExtent);

	rExtent.xValid = true;
	rExtent.yValid = true;
}

void CPGLContainer::UpdateExtent(gfxinterface::CGfxInterface& gfx)
{
	CPGLRectD rExtentTemp,rExtent;
	CPGLObjectPtr pObject;
	bool firstx=true;
	bool firsty=true;


	// no object case
	if (m_mObjects.IsEmpty())
	{
		SetExtent(CPGLRectD::GetNormBox());
	}

	// first, non text objects.
	rExtentTemp.xValid=false;
	rExtentTemp.yValid=false;
	rExtentTemp.left= std::numeric_limits<double>::max();
	rExtentTemp.right= -std::numeric_limits<double>::max();
	rExtentTemp.bottom= std::numeric_limits<double>::max();
	rExtentTemp.top= -std::numeric_limits<double>::max();

	CPGLObjectManager::Container::iterator it;
	for (it = m_mObjects.begin() ; it != m_mObjects.end(); it++)
	{
		pObject=*it;
		if (!pObject)
			continue;

		if (!pObject->IsVisible())
			continue;

		// test for type...
		pObject->GetUnionExtent(gfx,rExtentTemp);

		// testing...
		if (rExtentTemp.xValid)
		{
			rExtent.left=rExtentTemp.left;
			rExtent.right=rExtentTemp.right;
		}

		if (rExtentTemp.yValid)
		{
			rExtent.top=rExtentTemp.top;
			rExtent.bottom=rExtentTemp.bottom;
		}
	}

	if (rExtent.GetWidth() < std::numeric_limits<double>::denorm_min())
	{
		rExtent.right=rExtent.left+1;
	}
	else if (rExtent.GetHeight() < std::numeric_limits<double>::denorm_min())
	{
		rExtent.top=rExtent.bottom+1;
	}

	// building transformation matrix...
	// transforming extent...	
	std::list<STransform>::const_iterator is;
	double radius,angle;
	static STransform tr;
	for (is = m_lTransforms.begin(); is != m_lTransforms.end() ; is ++)
	{
		tr = *is;
		switch(tr.type)
		{
		case STransform::TypeRotation:
			// lower left...
			rExtent.left -= tr.point.x;
			rExtent.right -= tr.point.x;
			rExtent.top -= tr.point.y;
			rExtent.bottom -= tr.point.y;

			// rotate points...
			radius = sqrt(PGL_SQR(rExtent.left) + PGL_SQR(rExtent.bottom));
			angle = atan( rExtent.bottom / rExtent.left );
			if (rExtent.left < 0)
				angle += PGL_PI;

			// iniatialization of rExtentTemp...
			rExtentTemp.left = rExtentTemp.right = radius*cos(angle + tr.dValue);
			rExtentTemp.bottom = rExtentTemp.bottom = radius*sin(angle + tr.dValue);

			// lower right...
			radius = sqrt(PGL_SQR(rExtent.right) + PGL_SQR(rExtent.bottom));
			angle = atan( rExtent.bottom / rExtent.right );
			if (rExtent.right < 0)
				angle += PGL_PI;
			
			rExtentTemp.MakeUnionX(radius*cos(angle + tr.dValue));
			rExtentTemp.MakeUnionY(radius*sin(angle + tr.dValue));

			// upper right...
			radius = sqrt(PGL_SQR(rExtent.right) + PGL_SQR(rExtent.top));
			angle = atan( rExtent.top / rExtent.right );
			if (rExtent.right < 0)
				angle += PGL_PI;
			
			rExtentTemp.MakeUnionX(radius*cos(angle + tr.dValue));
			rExtentTemp.MakeUnionY(radius*sin(angle + tr.dValue));

			// upper left...
			radius = sqrt(PGL_SQR(rExtent.left) + PGL_SQR(rExtent.top));
			angle = atan( rExtent.top / rExtent.left );
			if (rExtent.left < 0)
				angle += PGL_PI;
			
			rExtentTemp.MakeUnionX(radius*cos(angle + tr.dValue));
			rExtentTemp.MakeUnionY(radius*sin(angle + tr.dValue));

	
			rExtent = rExtentTemp;

			// translating back
			rExtent.left += tr.point.x;
			rExtent.right += tr.point.x;
			rExtent.top += tr.point.y;
			rExtent.bottom += tr.point.y;
			break;
		case STransform::TypeScaling:
			// first translate to origin...
			rExtent.left -= tr.point.x;
			rExtent.right -= tr.point.x;
			rExtent.top -= tr.point.y;
			rExtent.bottom -= tr.point.y;

			// scale points...
			rExtent.left*= tr.poValue.x;
			rExtent.right*=tr.poValue.x;
			rExtent.bottom*=tr.poValue.y;
			rExtent.top*=tr.poValue.y;

			// translating back
			rExtent.left += tr.point.x;
			rExtent.right += tr.point.x;
			rExtent.top += tr.point.y;
			rExtent.bottom += tr.point.y;
			break;		
		}
	}

	SetExtent(rExtent);
}


void CPGLContainer::PushLinesTop(CPGLLegendPtr pLegend)
{
	if (! pLegend)
		return;

	m_mObjects.PushLinesTop(pLegend);
}
